package hydrap2p.gui;

import hydrap2p.ServiceLocation;
import hydrap2p.download.BaseDownload;
import hydrap2p.download.Download;
import hydrap2p.download.DownloadListener;
import hydrap2p.download.DownloadSubscriptionType;
import hydrap2p.download.Downloader;
import hydrap2p.download.DownloadPriority;
import hydrap2p.download.Source;
import hydrap2p.download.DownloadState;
import hydrap2p.gui.commands.Cmd_DetailsPanel_RefreshDetails;
import hydrap2p.gui.commands.Cmd_FileTransferPanel_RefreshDetails;
import hydrap2p.gui.commands.Cmd_SearchPanel_UpdateResults;
import hydrap2p.gui.commands.CommandInvoker;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.helper.ThriftServerThread;
import hydrap2p.helper.ThriftUtils;
import hydrap2p.helper.Utility;
import hydrap2p.library.HashData;
import hydrap2p.library.InvalidUID;
import hydrap2p.library.LibraryListener;
import hydrap2p.library.Range;
import hydrap2p.library.SharedItemInfo;
import hydrap2p.logging.Level;
import hydrap2p.logging.Logging;
import hydrap2p.logging.LoggingListener;
import hydrap2p.modulemanager.ModuleInfo;
import hydrap2p.servicemanager.NameTaken;
import hydrap2p.search.SearchListener;
import hydrap2p.search.SearchProvider;
import hydrap2p.search.SearchQuery;
import hydrap2p.search.SearchResult;
import hydrap2p.search.Searcher;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.BindException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Map.Entry;

import javax.swing.Timer;

import org.apache.log4j.PropertyConfigurator;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class GUIDataHandler implements DownloadListener.Iface, 
										LibraryListener.Iface,
										LoggingListener.Iface,
										SearchListener.Iface /*, ActionListener*/ {
	static {

		//Configure log4j
		Properties log4jProps = new Properties();
		log4jProps.setProperty("log4j.rootLogger", "TRACE, console");
		log4jProps.setProperty("log4j.appender.console", "org.apache.log4j.ConsoleAppender");
		log4jProps.setProperty("log4j.appender.console.layout", "org.apache.log4j.PatternLayout");
		log4jProps.setProperty("log4j.appender.console.layout.ConversionPattern", "%-4r [%t] %-5p %c %x - %m%n");

		PropertyConfigurator.configure(log4jProps);
	}

	// Class string name, doubling as logging marker
	private static final Marker TAG = MarkerFactory.getDetachedMarker("GUIDataHandler");

	// Console logger - used before connections established
	private static final Logger consoleLogger = LoggerFactory.getLogger(TAG.getName());
	
	private static volatile GUIDataHandler instance;
	
	private double bytesUploaded;
	private double bytesDownloaded;
	
	private ArrayList<Integer> searchIDList;
	
	private List<GUIDownloadListing> unmappedDownloadListing;
	private Map<Long,GUIDownloadListing> downloadListingMap;
	private List<Download> downloads;
	private List<MockModule> modules;
	private List<ModuleInfo> moduleInfoList;
	private List<MockLog> logs;
	
	//Threads
	private static ThriftServerThread downloadListener;
	private static ThriftServerThread libraryListener;
	private static ThriftServerThread loggingListener;
	private static ThriftServerThread searchListener;
	
	
	private List<String> supportedInterfaces;
	
	public static final byte LOG_CRITICAL = 90;
	public static final byte LOG_ERROR = 80;
	public static final byte LOG_WARNING = 70;
	public static final byte LOG_MISHAP = 50;
	public static final byte LOG_INFO = 30;
	public static final byte LOG_VERBOSE = 20;
	public static final byte LOG_DEBUG = 10;
	
	public static String DOWNLOAD_PATH = "./";
	
	private static final String DEFAULT_FRAME_WIDTH = "1024";
	private static final String DEFAULT_FRAME_HEIGHT = "800";
	private static final String DEFAULT_HORIZONTAL_DIVIDER_PERCENTAGE = "0.40";
	private static final String DEFAULT_VERTICAL_DIVIDER_PERCENTAGE = "0.20";
	private static final String PREFERENCES_FILE_PATH = Utility.getUserConfigurationPath()+"/gui_preferences.txt";
	
	
	public static synchronized GUIDataHandler getInstance(){
		if(instance == null){
			synchronized (GUIDataHandler.class){
				if(instance == null){
					System.out.println("Creating new handler instance...");
					instance = new GUIDataHandler();
				}
			}
		}
		return instance;
	}
	
	private GUIDataHandler(){
		// Initialize variables
		init();
	}
	
	public static void main(String[] args) {
		//Ensure module connections
		boolean manager = ThriftUtils.waitForManager();
		boolean library = ThriftUtils.waitForLibrary();
		boolean downloader = ThriftUtils.waitForDownloader();
		boolean logging = ThriftUtils.waitForLogging();
		
		GUIViewHandler.getInstance();
		System.out.println("Created GUI");
		
		GUIDataHandler handler = getInstance();
		
		try {
			downloadListener = ThriftUtils.initThriftServer(new DownloadListener.Processor(handler), "GUIData_download", 39239);
		} catch (BindException e) {
			consoleLogger.error(TAG, "Failed to start DownloadListener!  Could not bind to port.");
			e.printStackTrace();
			return;
		} catch (IOException e) {
			consoleLogger.error(TAG, "Failed to start DownloadListener!");
			e.printStackTrace();
			return;
		} catch (NameTaken e) {
			consoleLogger.error(TAG, "Failed to start DownloadListener!  Duplicate module exists.");
			e.printStackTrace();
			return;
		}
		
		try {
			libraryListener = ThriftUtils.initThriftServer(new LibraryListener.Processor(handler), "GUIData_library", 39240);
		} catch (BindException e) {
			consoleLogger.error(TAG, "Failed to start LibraryListener!  Could not bind to port.");
			e.printStackTrace();
			return;
		} catch (IOException e) {
			consoleLogger.error(TAG, "Failed to start LibraryListener!");
			e.printStackTrace();
			return;
		} catch (NameTaken e) {
			consoleLogger.error(TAG, "Failed to start LibraryListener!  Duplicate module exists.");
			e.printStackTrace();
			return;
		}
		
		try {
			loggingListener = ThriftUtils.initThriftServer(new LoggingListener.Processor(handler), "GUIData_logging", 39241);
		} catch (BindException e) {
			consoleLogger.error(TAG, "Failed to start LoggingListener!  Could not bind to port.");
			e.printStackTrace();
			return;
		} catch (IOException e) {
			consoleLogger.error(TAG, "Failed to start LoggingListener!");
			e.printStackTrace();
			return;
		} catch (NameTaken e) {
			consoleLogger.error(TAG, "Failed to start LoggingListener!  Duplicate module exists.");
			e.printStackTrace();
			return;
		}
		
		try {
			searchListener = ThriftUtils.initThriftServer(new SearchListener.Processor(handler), "GUIData_search", 39242);
		} catch (BindException e) {
			consoleLogger.error(TAG, "Failed to start SearchListener!  Could not bind to port.");
			e.printStackTrace();
			return;
		} catch (IOException e) {
			consoleLogger.error(TAG, "Failed to start SearchListener!");
			e.printStackTrace();
			return;
		} catch (NameTaken e) {
			consoleLogger.error(TAG, "Failed to start SearchListener!  Duplicate module exists.");
			e.printStackTrace();
			return;
		}
		
		if(manager && library && downloader) {
			downloadListener.start();
			libraryListener.start();
			loggingListener.start();
			searchListener.start();
		}
		else
			consoleLogger.error(TAG, "Failed to connect to essential modules!  Exiting.");
	
		// Subscribe to the provider modules
		handler.subscribeToServices();
	}
	
	private void init(){
		// Create and populate a list of interfaces supported by this class
		supportedInterfaces = new ArrayList<String>();
		supportedInterfaces.add("DownloadListener");
		supportedInterfaces.add("LibraryListener");
		supportedInterfaces.add("LoggingListener");
		supportedInterfaces.add("SearchListener");
		
		downloads = Collections.synchronizedList(new ArrayList<Download>()); // Create a local list of downloads
		unmappedDownloadListing = Collections.synchronizedList(new ArrayList<GUIDownloadListing>()); // Create a local list of parsed downloads
		downloadListingMap = Collections.synchronizedMap(new HashMap<Long, GUIDownloadListing>()); //
		
		modules = new ArrayList<MockModule>(); // Create a local list of the modules currently running
		logs = new ArrayList<MockLog>();		//local list of logs; ignore the alliteration and note that the last log is the most recent, so display them in reverse order
		
		//add fake downloads and modules so we have something to show
		//mockPopulate_downloadListing();
		//mockPopulate_modules();
		populateDownloadListingMap();
		populateModules();
		
		/* MOCK GRAPH STUFF
		timer = new Timer(1000, this);
        timer.setInitialDelay(1000);
        timer.start();
        bytesUploaded = 0;
        bytesDownloaded = 0;
        */
		
		System.out.println("*****************\n");
		
		for(long key : downloadListingMap.keySet()){
			System.out.println(downloadListingMap.get(key).getName());
			bytesUploaded += downloadListingMap.get(key).getBytesUploaded();
			bytesDownloaded += downloadListingMap.get(key).getBytesDownloaded();
		}
		
		System.out.println("*****************");
		
		
		searchIDList = new ArrayList<Integer>();
		
	}
	
	private void populateDownloadListingMap(){
		// Collect current data
		BaseDownload bd;
		GUIDownloadListing gdl;
		SharedItemInfo item;
		String hash = "Hash unavailable";
		Set<String> protocolList = new TreeSet<String>();
		String protocols = "";
		long uid;
		
		try {
			Map<Long, BaseDownload> downloads = ServiceConnector.getDownloader().getDownloads();
			
			for(Entry<Long, BaseDownload> entry : downloads.entrySet()){
				// Get raw download reference
				uid = entry.getKey();
				bd = entry.getValue();
				
				// Get raw shared item reference
				item = ServiceConnector.getLibrary().getSharedItemInfo(uid);

				consoleLogger.debug("Item: " + item);
				
				try {
					// Get correct hash
					for(HashData hd : item.getTargetHashes()){
						if(hd != null) {
							if(hd.getRange().equals(bd.getAvailable().get(0))){
									hash = hd.getHashdata() + " [" + hd.getAlgorithm() + "]";
								break;
							}
						}
					}
				} catch(NullPointerException e) {
					consoleLogger.warn("NullPointerException loading downloads!");
					e.printStackTrace();
				}
				
				// Get protocols
				for(Source src : bd.getSources()){
					protocolList.add(src.getProtocol());
				}
				for(String str : protocolList){
					protocols += str + " ";
				}
				
				// Construct download listing
				gdl = new GUIDownloadListing(item.getName(), protocols, hash,
						bd.getState().toString(), new File(item.getPathname()).getParent(), bd.getSourcesSize(), 0, bd.getPriority(), bd.getAvailableSize(), bd.getDownloadedSize(), 0);
				gdl.addContent(item.getName());
				for(Source src : bd.getSources()){
					gdl.addSource(src);
				}
				
				// Map download listing to uid
				downloadListingMap.put(uid, gdl);

			}
		} catch (TException e) {
			e.printStackTrace();
		} catch (InvalidUID e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Populates the download listing array with mock objects.
	 */
	/*private void mockPopulate_downloadListing(){
		GUIDownloadListing listing1 = new GUIDownloadListing("download 1", "HTTP", "ABCDEFGHIJ", "ACTIVE", "D:\\Downloads", 1, 0, 1, 5234, 1975, 0);
		listing1.addLabel("test3");
		listing1.addContent("minesweeper.exe");
		GUIDownloadListing listing2 = new GUIDownloadListing("o hai", "BitTorrent", "8x4Sa99Q1g", "ACTIVE", "D:\\BitTorrent\\Lolcat pictures", 0, 0, 3, 3011178, 3011178, 9912301); 
		listing2.addLabel("test3");
		listing2.addLabel("test4");
		listing2.addContent("cat_fights_printer.wmv");
		listing2.addContent("ninja_cat.avi");
		listing2.addContent("funny_sleeping_cat.wmv");
		GUIDownloadListing listing3 = new GUIDownloadListing("warez", "BitTorrent", "JD83M1Njja", "PAUSED", "D:\\BitTorrent\\Full Games", 0, 0, 5, Long.valueOf("4103997478"), 171975122, 129185);
		listing3.addContent("gta4.iso");
		listing3.addContent("gta4_keygen.exe");
		GUIDownloadListing listing4 = new GUIDownloadListing("linux", "BitTorrent", "oAIE4281Vs", "ACTIVE", "D:\\BitTorrent\\Operating Systems", 0, 56, 2, 208860713, 208860713, 400123195);
		listing4.addLabel("test8");
		listing4.addContent("ubuntu_karmic_koala.iso");
		GUIDownloadListing listing5 = new GUIDownloadListing("big file", "HTTP", "89XywLP321", "PAUSED", "D:\\Downloads", 1, 0, 4, Long.valueOf("912341000102341"), Long.valueOf("712341000102341"), 0); 
		listing5.addLabel("test2");
		listing5.addLabel("test4");
		listing5.addLabel("test9");
		listing5.addContent("the_complete_library_of_congress.txt");
		
		unmappedDownloadListing.add(listing1);
		unmappedDownloadListing.add(listing2);
		unmappedDownloadListing.add(listing3);
		unmappedDownloadListing.add(listing4);
		unmappedDownloadListing.add(listing5);
	}
	//*/
	private void populateModules(){
		try {
			moduleInfoList = ServiceConnector.getModuleManager().getModuleList();
			
		} catch (TException e) {
			e.printStackTrace();
		}
	}

	public List<GUIDownloadListing> getDownloadListing(){
		List<GUIDownloadListing> listings = new ArrayList<GUIDownloadListing>();

		listings.addAll(unmappedDownloadListing);
		
		for(Entry<Long, GUIDownloadListing> entry : downloadListingMap.entrySet()) {
			listings.add(entry.getValue());
		}
		return listings;
//		return unmappedDownloadListing;
	}
	
	public List<MockModule> getModules(){
		modules.clear();
		populateModules();
		
		for(ModuleInfo mi : moduleInfoList){
			modules.add(new MockModule(mi.getName(), mi.isRunning(), mi.isAutostart()));
		}
		
		return modules;
	}
	
	public void removeModule(int moduleIndex){
		String userPath = Utility.getUserConfigurationPath();
		File userFile;
		File confFile;
		try{
			setModuleAutoStartStatus(moduleIndex, false);
			setModuleRunningStatus(moduleIndex, false);
			
			//ServiceConnector.getServiceManager().getModuleList().remove(moduleIndex);
		}catch(Exception ex){
			System.out.println("An error occurred during removal of the selected module's .ini file.");
		}
	}
	
	public void setModuleAutoStartStatus(int moduleIndex, boolean isAutostart){
		String name = moduleInfoList.get(moduleIndex).getName();
		try {
			ServiceConnector.getModuleManager().setModuleAutostart(name, "local", isAutostart);
		} catch (TException e) {
			e.printStackTrace();
		}
	}
	
	public void setModuleRunningStatus(int moduleIndex, boolean isRunning){
		String name = moduleInfoList.get(moduleIndex).getName();
		if(isRunning){
			try {
				ServiceConnector.getModuleManager().startModule(name, "local");
			} catch (TException e) {
				e.printStackTrace();
			}
		}else{
			try {
				ServiceConnector.getModuleManager().stopModule(name, "local");
			} catch (TException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	public List<MockLog> getLogs(){
		return logs;
	}
	
	public void addToDownloadQueue(List<Source> sources, String destDir, String destName){
		System.out.println("Requesting download...\n" +
				"Directory: " + destDir + "\n" +
				"Filename: " + destName);
		try {
			ServiceConnector.getDownloader().createDownload(sources, destDir, destName, 0);
		} catch (TException e) {
			e.printStackTrace();
		}
	}
	
	public void addToDownloadListing(GUIDownloadListing gdl){
		// TODO There will be a Map object with a GDL key and a Download value, so the implementation will need to change
		// downloader.createDownload(gdl.getSources(), gdl.getDownloadLocation(), gdl.getName(), gdl.getSize());
		Downloader.Client downloader = ServiceConnector.getDownloader(); 
		
		System.out.println("Creating download...\n" +
				"Directory: " + gdl.getDownloadLocation() + "\n" +
				"Filename: " + gdl.getName() + "\n" +
				"Size: " + gdl.getSize());
		unmappedDownloadListing.add(gdl);
		
		/*
		Random rnd = new Random();
		
		Source source = new Source("mirrors.easynews.com", 80, "HTTP", "", "", null, Arrays.asList(new Range(0, 17514512)), null);
		Map<String, String> extras = new TreeMap<String, String>();
		extras.put("path", "/linux/ubuntu/dists/lucid/Contents-amd64.gz");
		source.setExtra(extras);
		
		List<Source> sources = new ArrayList<Source>();
		sources.add(source);
		//*/
		try {
			//TODO may need to spawn a thread for these calls
			//downloader.createDownload(sources, "C:/", "beepboop" + rnd.nextInt(10000) + ".gz", 17514512);
			downloader.createDownload(gdl.getSources(), gdl.getDownloadLocation(), gdl.getName(), gdl.getSize());
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
//		Source source = new Source("kittens", 80, "bittorrent", "", "", null, null, null);
//		Map<String, String> extras = new TreeMap<String, String>();
//		extras.put("torrent_path", "C:/testTorrentJoCo.torrent");
//		source.setExtra(extras);
//		
//		try {
//			downloader.createDownload(Arrays.asList(source), "C:/", null, -1);
//		} catch(TException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
	}
	
	public void removeFromDownloadListing(GUIDownloadListing gdl){
		// TODO There will be a Map object with a GDL key and a Download value, so the implementation will need to change
		//downloader.remo
		System.out.println("Removing download...\n" +
				"Directory: " + gdl.getDownloadLocation() + "\n" +
				"Filename: " + gdl.getName() + "\n" +
				"Size: " + gdl.getSize());
		try{
			if(unmappedDownloadListing.contains(gdl)){
				unmappedDownloadListing.remove(gdl);
			}else if(downloadListingMap.containsValue(gdl)){
				for(Entry<Long, GUIDownloadListing> e : downloadListingMap.entrySet()){
					if(e.getValue().equals(gdl)){
						ServiceConnector.getDownloader().removeDownload(e.getKey());
						downloadListingMap.remove(e.getKey());
						return;
					}
				}
				
			}
		}catch(TException te){
			System.out.println("Unable to remove the download " + gdl.getName());
		}
		//unmappedDownloadListing.remove(gdl);
	}
	
	public void removeFromDownloadListing(int index){
		// TODO There will be a Map object with a GDL key and a Download value, so the implementation will need to change
		//removeFromDownloadListing(unmappedDownloadListing.get(index));
		//downloadListing.remove(index);
	}
	
	public ArrayList<String> loadPreferences(){
		ArrayList<String> prefs = new ArrayList<String>();
		String s;
		try{
			File file = new File(PREFERENCES_FILE_PATH);
			Scanner reader = new Scanner(file);
			while(reader.hasNext()){
				s = reader.next();
				if(s.trim().length() > 0){
					prefs.add(s);
					System.out.println(s);
				}
			}
			reader.close();
		}catch(Exception ex){
			prefs.clear();
			prefs.add(DEFAULT_FRAME_WIDTH);
			prefs.add(DEFAULT_FRAME_HEIGHT);
			prefs.add(DEFAULT_HORIZONTAL_DIVIDER_PERCENTAGE);
			prefs.add(DEFAULT_VERTICAL_DIVIDER_PERCENTAGE);
			prefs.add(DOWNLOAD_PATH);
		}
		
		return prefs;
	}
	
	public void savePreferences(ArrayList<String> prefs){
		try{
			File file = new File(PREFERENCES_FILE_PATH);
			PrintWriter writer = new PrintWriter(file);
			for(String s : prefs){
				writer.write(s + "\n");
			}
			writer.close();
		}catch(Exception ex){
			ex.printStackTrace();
		}
		
	}
	
	public void subscribeToServices(){
		System.out.println("Subscribing to services...");

		try {
			System.out.println("Subscribing to downloader...");
			Downloader.Client downloader = ServiceConnector.getDownloader();
			DownloadSubscriptionType type = new DownloadSubscriptionType();
			type.setAll(true);
			
			downloader.subscribeListener(new ServiceLocation("localhost", downloadListener.getLocation().port), type); // What's a DownloadSubscriptionType?
			System.out.println("Subscribed to downloader.");
			
		} catch (TException e) {
			e.printStackTrace();
			System.out.println("FAILED to subscribe to downloader.");
		}
		//*/
		
		try {
			System.out.println("Subscribing to searcher...");
			Searcher.Client searcher = ServiceConnector.getSearcher();
			
			searcher.subscribeListener(new ServiceLocation("localhost", searchListener.getLocation().port));
			System.out.println("Subscribed to searcher.");
			
		} catch (TException e) {
			e.printStackTrace();
			System.out.println("FAILED to subscribe to searcher.");
		}
		//*/
	}
	
	public void unsubscribeFromServices(){
		System.out.println("Unsubscribing from services...");
		
		try {
			Downloader.Client downloader = ServiceConnector.getDownloader();
			if (downloader!=null && downloadListener!=null && downloadListener.getLocation()!=null)
				downloader.unsubscribeListener(new ServiceLocation("localhost", downloadListener.getLocation().port));
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//*/
		
		try {
			Searcher.Client searcher = ServiceConnector.getSearcher();
			if (searcher!=null && searchListener!=null && searchListener.getLocation()!=null)
				searcher.unsubscribeListener(new ServiceLocation("localhost", searchListener.getLocation().port));
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//*/
		
		// Disconnect from the Manager
		if (GUIDataHandler.downloadListener!=null)
			GUIDataHandler.downloadListener.shutdown();
		if (GUIDataHandler.searchListener!=null)
			GUIDataHandler.searchListener.shutdown();
		if (GUIDataHandler.libraryListener!=null)
			GUIDataHandler.libraryListener.shutdown();
		if (GUIDataHandler.loggingListener!=null)
			GUIDataHandler.loggingListener.shutdown();
		//*/
	}
	
	// ///////////////////////////////////////////////////////////////////////////////////////////////////
	// Inherited methods /////////////////////////////////////////////////////////////////////////////////
	// ///////////////////////////////////////////////////////////////////////////////////////////////////


	@Override
	/**
	 * Indicates that a new download has been created
	*/
	public void newDownload(long download) throws TException {
		//Reconnect downloader for thread safety
		//This method can be called by the swing event thread and cause explosions
		Downloader.Client downloader = ServiceConnector.getDownloader();
		
		// Assign incoming ID to last unmapped download
		if(unmappedDownloadListing.size() > 0){
			BaseDownload bd = downloader.getDownloadWithUid(download);
			int last = unmappedDownloadListing.size() - 1;
			GUIDownloadListing listing = unmappedDownloadListing.get(last);
			
			listing.setPriority(bd.getPriority());
			
			//Set protocols
			listing.setProtocol("");
			List<String> usedProtocols = new ArrayList<String>();
			for(Source s : bd.getSources()) {
				if(!usedProtocols.contains(s.getProtocol().toLowerCase())) {
					listing.setProtocol(listing.getProtocol() + s.getProtocol() + ", ");
					usedProtocols.add(s.getProtocol().toLowerCase());
				}
			}
			
			listing.setBytesDownloaded(bd.getDownloadedSize());
			listing.setStatus(bd.getState().toString());
			
			downloadListingMap.put(download, listing);
			unmappedDownloadListing.remove(last);

			System.out.println("Added new download with uid " + download);
		} else {
			//System.out.println("No unmapped download to add to");
			//GUIDownloadListing gdl = new GUIDownloadListing(filePath.getName(), protocol, "(no associated hash)",
			//		GUIDownloadListing.PAUSED, filePath.getParent(), 1, 0, Priority.NORMAL, size, 0, 0);
			BaseDownload bd = ServiceConnector.getDownloader().getDownloadWithUid(download);
			String fileName = bd.toString();
			
			// Collect current data
			GUIDownloadListing gdl;
			SharedItemInfo item;
			String hash = "Hash unavailable";
			Set<String> protocolList = new TreeSet<String>();
			String protocols = "";
			
			try {
				// Get raw shared item reference
				item = ServiceConnector.getLibrary().getSharedItemInfo(download);

				consoleLogger.debug("Item: " + item);

				try {
					// Get correct hash
					for(HashData hd : item.getTargetHashes()){
						if(hd != null) {
							if(hd.getRange().equals(bd.getAvailable().get(0))){
								hash = hd.getHashdata() + " [" + hd.getAlgorithm() + "]";
								break;
							}
						}
					}
				} catch(NullPointerException e) {
					consoleLogger.warn("NullPointerException loading downloads!");
					e.printStackTrace();
				}

				// Get protocols
				for(Source src : bd.getSources()){
					protocolList.add(src.getProtocol());
				}
				for(String str : protocolList){
					protocols += str + " ";
				}

				// Construct download listing
				gdl = new GUIDownloadListing(item.getName(), protocols, hash,
						bd.getState().toString(), new File(item.getPathname()).getParent(), bd.getSourcesSize(), 0, bd.getPriority(), bd.getAvailableSize(), bd.getDownloadedSize(), 0);
				gdl.addContent(item.getName());
				for(Source src : bd.getSources()){
					gdl.addSource(src);
				}

				// Map download listing to uid
				downloadListingMap.put(download, gdl);

			} catch (TException e) {
				e.printStackTrace();
			} catch (InvalidUID e) {
				e.printStackTrace();
			}

		}
	}

	@Override
	/**
	 * Indicates that a download source has been added or changed
	*/
	public void newSource(long download, Source source) throws TException {
		// There is a 1:n relationship between downloads and sources
		downloadListingMap.get(download).addSource(source);
		
	}

	@Override
	/**
	 * Indicates that the download's percentage downloaded has changed to a new value
	*/
	public void updateDownloadProgress(long uid, Source source, long downloaded, long total)
			throws TException {
		System.out.println("Updating download progress");
		System.out.println("Downloaded: " + downloaded);
		System.out.print("Total: " + total);
		
		// Get reference to download
		GUIDownloadListing gdl = downloadListingMap.get(uid);
		
		// Update bytes downloaded
		gdl.setBytesDownloaded(total);
		
		// Check for completion
		if(total == gdl.getSize()){
			updateDownloadState(uid, DownloadState.STOPPED);
			gdl.updateDownloadSpeed(source.getIp(), 0);
		} else {
			gdl.updateDownloadSpeed(source.getIp(), downloaded);
		}
		//*/
		
		CommandInvoker.getInstance().invokeCommand(new Cmd_FileTransferPanel_RefreshDetails());
		CommandInvoker.getInstance().invokeCommand(new Cmd_DetailsPanel_RefreshDetails());
		
	}

	/**
	 * Updates the displayed status for this source
	*/
	//@Override
	public void updateSourceStatus(long uid, Source source, String status)
			throws TException {
		// TODO Is this necessary (shallow copy carryover)?
		//downloadListingMap.get(uid).
	}


	@Override
	/**
	 * Indicates that the download's percentage verified has changed to a new value
	*/
	public void updateVerifiedProgress(long uid, long verified, long total)
			throws TException {
		if(downloadListingMap.containsKey(uid)){
			// TODO 
			
		}
	}

	@Override
	public void ping() throws TException {
//		System.out.println("GUIDataHandler is being pinged.");
	}

	@Override
	public List<String> supportedInterfaces() throws TException {
		return supportedInterfaces;
	}

	@Override
	/**
	 * Notifies this listener that the shared file has changed its extra data
	 */
	public void changedExtra(SharedItemInfo file) throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	/**
	 * Notifies this listener that the shared file has changed its metadata
	 */
	public void changedMetadata(SharedItemInfo file) throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	/**
	 * Indicates that this shared file has removed a new downloaded chunk
	 */
	public void deletePartialProgress(SharedItemInfo file, Range range)
			throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	/**
	 * Indicates that this shared file has removed a new verified chunk
	 */
	public void deleteVerifiedProgress(SharedItemInfo file, Range range)
			throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	/**
	 * Indicates that this shared file has a new downloaded chunk
	 */
	public void newPartialProgress(SharedItemInfo file, Range range)
			throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	/**
	 * Notifies this listener that an entirely new shared item has finished hashing and searching metadata
	 */
	public void newSharedItem(SharedItemInfo file) throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	/**
	 * Indicates that this shared file has a new verified chunk
	 */
	public void newVerifiedProgress(SharedItemInfo file, Range range)
			throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	/**
	 * Notifies this listener that an entirely new shared item has finished hashing and searching metadata
	 */
	public void removedSharedItem(SharedItemInfo file) throws TException {
		// TODO Auto-generated method stub
		
	}

	/**
	 * A LoggingListener is an object that receives log events and processes them in some manner
	 */
	public void log(byte severity, String logEntrySource, String logMessage) throws TException {
		/*
		Each log listener subscribes to receive log levels at a certain level, where 100 is the highest and 0 is the lowest

		Other recommended levels:
			90 - Critical Error
			80 - Error
			70 - Warning
			50 - Mild mishap
			30 - Informative information
			20 - Verbose information
			10 - Debug information
		*/
		
		String message; // Will provide entire message formatted into one String
		
		// Determine severity
		switch(severity){
		case LOG_VERBOSE:
			message = "Verbose information from ";
			break;
		case LOG_INFO:
			message = "Information from ";
			break;
		case LOG_DEBUG:
			message = "Debug information from ";
			break;
		case LOG_WARNING:
			message = "Warning reported in ";
			break;
		case LOG_ERROR:
			message = "Error reported in ";
			break;
		case LOG_CRITICAL:
			message = "Critical error reported in ";
			break;
		case LOG_MISHAP:
			message = "Mishap reported in ";
			break;
		default:
			message = "Unrecognized log (Code " + severity + ") from ";
		}
		
		// Identify source
		if(logEntrySource != null){
			message += logEntrySource;
		}else{
			message += "unidentified object";
		}
		
		// Attach message body
		if(logMessage != null){
			message += ": " + logMessage;
		}else{
			message += ": (no details)";
		}
		
		//timestamped from within the constructor
		logs.add(new MockLog(message));
		
		//make sure the change shows up
		LoggingPanel.getInstance().refreshDetails();
	}

	public int submitSearchQuery(SearchObject so){
		SearchQuery query = new SearchQuery();

		String terms = "";
		for(String term : so.getTerms()){
			terms += term + " ";
		}
		query.setKeywords(terms);
		query.setName(terms);
		
		terms = "";
		for(String term : so.getAllTerms()){
			terms += term + " ";
		}
		query.putToMetadata("all", terms);
		
		terms = "";
		for(String term : so.getAnyTerms()){
			terms += term + " ";
		}
		query.putToMetadata("any", terms);
		
		terms = "";
		for(String term : so.getNoTerms()){
			terms += term + " ";
		}
		query.putToMetadata("not", terms);
		
		try {
			//return ServiceConnector.getSearcher().startSearch(query);
			return ServiceConnector.getSearcher().startSearchSome(query, so.getProtocols());
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return -1;
	}
	
	public void stopSearch(int searchID){
		try {
			ServiceConnector.getSearcher().stopSearch(searchID);
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public List<SearchResult> getSearchResults(int searchID){
		try {
			return ServiceConnector.getSearcher().getSearchResults(searchID);
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public void addSearchResult(int arg0, SearchResult arg1) throws TException {
		// TODO Auto-generated method stub
		CommandInvoker.getInstance().invokeCommand(new Cmd_SearchPanel_UpdateResults());
		
	}

	@Override
	public void delSearch(int searchID) throws TException {
		// TODO Auto-generated method stub
		searchIDList.remove((Integer)searchID);
		ServiceConnector.getSearcher().unsubscribeSearchListener(searchListener.getLocation(), searchID);
		// TODO Fix logging here
		log(LOG_VERBOSE, "Search Module", "Search deleted");
	}

	@Override
	public void newSearch(int searchID, SearchQuery query, List<String> providers) throws TException {
		// TODO Auto-generated method stub
		searchIDList.add(searchID);
		ServiceConnector.getSearcher().subscribeSearchListener(searchListener.getLocation(), searchID); // Subscribes to progressive updates from this particular search
		CommandInvoker.getInstance().invokeCommand(new Cmd_SearchPanel_UpdateResults());
		// TODO Fix logging here
		log(LOG_VERBOSE, "Search Module", "Search started");
	}

	@Override
	public void searchFinished(int searchID) throws TException {
		// TODO Auto-generated method stub
		//CommandInvoker.getInstance().invokeCommand(new Cmd_SearchPanel_UpdateResults());
		searchIDList.remove((Integer)searchID);
		ServiceConnector.getSearcher().unsubscribeSearchListener(searchListener.getLocation(), searchID);
		SearchPanel.getInstance().searchIsFinished();
		// TODO Fix logging here
		log(LOG_VERBOSE, "Search Module", "Search finished");
	}

	public void searchProviderStarted(int arg0, String arg1) throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void searchProviderFinished(int arg0, String arg1) throws TException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void removedDownload(long uid) throws TException {
		if(downloadListingMap.containsKey(uid)){
			GUIDownloadListing gdl = downloadListingMap.remove(uid);
			// Account for 
			bytesUploaded -= gdl.getBytesUploaded();
			bytesDownloaded -= gdl.getBytesDownloaded();
			
		}
	}

	@Override
	public void removedSource(long uid, Source source) throws TException {
		if(downloadListingMap.containsKey(uid)){
			downloadListingMap.get(uid).removeSource(source);
		}
	}

	@Override
	public void updateAvailableRanges(long uid, List<Range> ranges)
			throws TException {
		if(downloadListingMap.containsKey(uid)){
			// TODO Add functionality to GUIDownloadingListing for Range
			//downloadListingMap.get(uid).;
		}
	}

	@Override
	public void updateDownloadedRanges(long uid, List<Range> ranges)
			throws TException {
		if(downloadListingMap.containsKey(uid)){
			// TODO Add functionality to GUIDownloadingListing for Range
			//downloadListingMap.get(uid).;
		}
	}

	@Override
	public void updateSource(long uid, Source source) throws TException {
		if(downloadListingMap.containsKey(uid)){
			downloadListingMap.get(uid).addSource(source);
		}
	}

	@Override
	public void updateVerifiedRanges(long uid, List<Range> ranges)
			throws TException {
		if(downloadListingMap.containsKey(uid)){
			// TODO Add functionality to GUIDownloadingListing for Range
			//downloadListingMap.get(uid).;
		}
	}


	/* MOCK GRAPH STUFF
	@Override
	public void actionPerformed(ActionEvent arg0) {
		//the only action should be a timer event firing, so assume it's that
		double temp = 0; // Create holder variable
		
		//double fakeUpSpeed = Math.random() * speedMax;
		//double fakeDownSpeed = Math.random() * speedMax;
		
		double upSpeed = 0;
		double downSpeed = 0;

		
		// Collect current data
		for(Long key : downloadListingMap.keySet()){
			upSpeed += downloadListingMap.get(key).getBytesUploaded();
			downSpeed += downloadListingMap.get(key).getBytesDownloaded();
		}
		
		// Get upload speed
		temp = bytesUploaded; // Hold onto previous number of bytes uploaded
		bytesUploaded = upSpeed; // Set current number of bytes uploaded for later
		upSpeed -= temp; // Get the increment from last measure;
		upSpeed = upSpeed * 1000 / timer.getDelay(); // Make sure the interval measures on a per-second basis
		
		// Get download speed
		temp = bytesDownloaded; // Hold onto previous number of bytes downloaded
		bytesDownloaded = downSpeed; // Set current number of bytes downloaded for later
		downSpeed -= temp; // Get the increment from last measure;
		downSpeed = downSpeed * 1000 / timer.getDelay(); // Make sure the interval measures on a per-second basis
		
		System.out.println("Action fired; values are " + fakeUpSpeed + " and " + fakeDownSpeed);
		
		//((GraphPanel)LoggingPanel.getInstance().graphArea).addSpeedPair(fakeUpSpeed, fakeDownSpeed);
		((GraphPanel)LoggingPanel.getInstance().graphArea).addSpeedPair(upSpeed, downSpeed);
		
		// TODO Make sure to account for removals from the downloadListingMap; on remove, subtract the number of bytes downloaded from that file from bytesDownloaded; do same for uploaded
	}
	
	//private double speedMax = 10.0;
	 */
	
	@Override
	public void updateDownloadState(long uid, DownloadState newState) throws TException {
		if(downloadListingMap.containsKey(uid)){
			downloadListingMap.get(uid).setStatus(newState.toString());
		}
	}
	
	public void setDownloadPriority(GUIDownloadListing gdl, DownloadPriority newPriority) throws TException {
		if(downloadListingMap.containsValue(gdl)){
			for(Entry<Long, GUIDownloadListing> e : downloadListingMap.entrySet()){
				if(e.getValue().equals(gdl)){
					ServiceConnector.getDownloader().setDownloadPriority(e.getKey(), newPriority);
					return;
				}
			}
		}
	}

	public void setDownloadState(GUIDownloadListing gdl, DownloadState newState) throws TException {
		if(downloadListingMap.containsValue(gdl)){
			for(Entry<Long, GUIDownloadListing> e : downloadListingMap.entrySet()){
				if(e.getValue().equals(gdl)){
					ServiceConnector.getDownloader().setDownloadState(e.getKey(), newState);
					return;
				}
			}
		}
	}
	
	public void updateSharedItemLabels(GUIDownloadListing gdl, String newLabels){
		if(downloadListingMap.containsValue(gdl)){
			for(Entry<Long, GUIDownloadListing> e : downloadListingMap.entrySet()){
				if(e.getValue().equals(gdl)){
					try {
						ServiceConnector.getDownloader().setDownloadExtra(e.getKey(), "labels", newLabels);
					} catch (TException ex) {
						ex.printStackTrace();
					}
					return;
				}
			}
		}
	}

	@Override
	public void log(Level arg0, String arg1, String arg2) throws TException {
		// TODO Auto-generated method stub
		
	}

}
		


